package com.itany.mall.factory;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

// 完整类名, 可以在运行时, 对类加载目录进行遍历, 获取出所有".class"文件
// 对文件路径名进行处理, 得到"包名.类名"
// 就可以Class.forName()获取类了
// 在程序运行中, 检测某个类是否需要创建对象
// 不需要配置文件
// 通过注解给需要创建对象的类加一个标记, 在运行中通过获取标记并判断是否为null
// 决定是否要创建对象
// 要创建对象的类的key, 通过标记携带(注解的属性)
// 对于类中某个成员变量需在装配对象的, 也可以使用注解为标记
// 让工厂解析标记来进行对象的装配工作
public class ObjectFactory {

	// 类加载目录的File对象, 进行目录的递归遍历时使用
	private static final File CLASS_LOAD_DIR;

	// 类加载目录的路径字符串, 对".class"文件路径进行处理获得"包名.类名"时使用
	private static final String CLASS_LOAD_PATH;

	// 工厂容器: 存储需要创建实例的类的Class对象
	private static final Map<String, Class> CLASSES;

	// 工厂容器: 存储工厂创建出的实例
	private static final Map<String, Object> BEANS;

	// 静态代码块: 工厂初始化操作
	static {
		System.out.println("工厂启动, 开始初始化...");
		// ClassLoader可以从类加载目录加载资源
		// 可以利用它获得类加载目录的path
		// ClassLoader的"getResource()"方法
		// 当参数为""时, 表示获取当前目录(对类加载器而言, "当前目录"就是类加载目录)
		String p = ObjectFactory.class.getClassLoader().getResource("").getPath();
		CLASS_LOAD_DIR = new File(p);

		// 根据类加载目录的File对象, 获取类加载目录路径字符串
		CLASS_LOAD_PATH = CLASS_LOAD_DIR.getAbsolutePath();

		CLASSES = new HashMap<>();
		BEANS = new HashMap<>();

		try {
			// 扫描类加载目录
			scan(CLASS_LOAD_DIR);

			// 扫描完成之后, 进行属性对象的装配工作
			autowired();
		} catch (Exception e) {
			e.printStackTrace();
			throw new ExceptionInInitializerError("工厂初始化失败");
		}

		System.out.println("工厂初始化完成!");
	}

	// 获取目录内容时, 只获取子目录和".class"文件: 用到过滤器
	private static final FileFilter filter = new FileFilter() {
		@Override
		public boolean accept(File pathname) {
			return pathname.isDirectory() || pathname.getName().endsWith(".class");
		}
	};

	/**
	 * 递归扫描类加载目录, 对所有的".class"文件进行处理
	 * 将文件路径处理成className, 再通过反射Class.forName()获取类的Class对象
	 * 再获取类的注解(@Component), 如果某个类上能够获取到该注解
	 * 说明该类是需要由工厂创建实例的
	 * key: 通过注解属性获取
	 * @throws Exception 
	 */
	private static void scan(File file) throws Exception {
		// 如果file是目录, 则获取目录内容, 遍历并递归
		if (file.isDirectory()) {
			// 根据过滤器规则获取目录内容
			File[] fs = file.listFiles(filter);
			for (File f : fs) {
				scan(f);
			}
		}

		String path = file.getAbsolutePath();

		// 如果目标文件不是".class"结尾, 跳过
		if (!path.endsWith(".class")) {
			return;
		}

		// 处理文件路径: path
		// 将路径字符串中, "类加载目录路径字符串"部分去掉
		path = path.replace(CLASS_LOAD_PATH, "");

		// 从字符串中截取出包+类部分
		path = path.substring(1, path.lastIndexOf(".class"));

		// 将字符串中的路径分隔符替换成".", 得到className
		String className = path.replaceAll("[/|\\\\]", ".");

		// 得到Class对象
		Class c = Class.forName(className);

		// 获取类上的@Component注解, 并判断
		Component com = (Component) c.getAnnotation(Component.class);
		// 如果com为null, 说明类上没有@Component注解, 工厂不管理这个类, 跳过
		if (com == null) {
			return;
		}

		// 从注解属性中获取类对应的key
		String key = com.value();

		// 如果key值不为空, 将Class与对应的key存入容器, 跳过
		if (key != null && !key.trim().isEmpty()) {
			CLASSES.put(key, c);
			System.out.println("交给工厂管理, 进行实例创建, 属性装配的类: " + key + ": " + c);
			return;
		}

		// 如果key值为空, 获取目标类所实现的接口
		Class[] interfaces = c.getInterfaces();

		// 如果目标类有接口
		// 将接口的简单名称首字母小写作为key
		// 将目标类与key存入容器备用, 完成后跳过
		if (interfaces != null && interfaces.length > 0) {
			for (Class i : interfaces) {
				char[] arr = i.getSimpleName().toCharArray();
				arr[0] = Character.toLowerCase(arr[0]);
				String k = new String(arr);
				CLASSES.put(k, c);
				System.out.println("交给工厂管理, 进行实例创建, 属性装配的类: " + k + ": " + c);
			}
			return;
		}

		// 如果目标类没有接口, 就直接用类的简单名称首字母小写做为key
		char[] arr = c.getSimpleName().toCharArray();
		arr[0] = Character.toLowerCase(arr[0]);
		String k = new String(arr);
		CLASSES.put(k, c);
		System.out.println("交给工厂管理, 进行实例创建, 属性装配的类: " + k + ": " + c);
	}

	// 属性自动装配对象
	// 根据容器中的key, 将各个对象获取出来
	// 对对象的属性进行装配
	private static void autowired() {
		for (String key : CLASSES.keySet()) {
			Object obj = getObject(key);
			autowired(obj);
		}
	}

	/**
	 * 向指定对象的属性进行装配
	 * 通过反射技术, 扫描对象的成员变量
	 * 获取成员变量上的"@Autowired"注解
	 * 如果不为null, 说明该成员变量需要由工厂装配对象
	 * 就从容器是将需要的对象获取出来, 装配上
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	public static void autowired(Object obj) {
		Class c = obj.getClass();
		Field[] fs = c.getDeclaredFields();
		// 如果这个对象没有成员变量, 跳过
		if (fs == null || fs.length == 0) {
			return;
		}

		// 如果对象中有成员变量带注解, 则对各个成员变量进行装配操作
		for (Field f : fs) {
			f.setAccessible(true); // 将成员变量设置为可访问(临时性的, 为了工厂的操作)

			// 获取成员变量头上的注解
			Autowired a = f.getAnnotation(Autowired.class);

			// 如果成员变量头上没有注解, 跳过
			if (a == null) {
				continue;
			}

			// 如果注解value有值, 则根据注解的属性值为key进行对象的获取装配
			String key = a.value();
			if (key == null || key.isEmpty()) {
				// 如果注解的value没有值, 则以成员变量的类型名称首字母小字为key
				// 这跟工厂容器中的key是能对应上的
				// 因为在向工厂中存入时, 使用的key要么是接口名要么是类名, 首字母小写
				// 与这里的成员变量类型名首字母小写能对应得上
				char[] arr = f.getType().getSimpleName().toCharArray();
				arr[0] = Character.toLowerCase(arr[0]);
				key = new String(arr);
			}

			try {
				f.set(obj, getObject(key));
				System.out.println("工厂向[ " + obj + " ]的 [" + f + "] 装配实例");
			} catch (Exception e) {
				throw new RuntimeException("工厂装配属性时出错", e);
			}

		}
	}

	// 公共的获取对象的方法
	public static Object getObject(String key) {
		Object obj = BEANS.get(key);
		try {
			if (obj == null) {
				Class c = CLASSES.get(key);
				obj = c.newInstance();
				BEANS.put(key, obj);
			}
		} catch (Exception e) {
			e.printStackTrace();
			// 当发生异常, 则说明从工厂的容器中获取对象失败
			// 从而导致程序运行存在安全隐患
			// 应当让程序终止, 让程序员检查并排除问题
			throw new RuntimeException("从工厂获取对象 " + key + " 失败", e);
		}
		System.out.println("从工厂中获取出实例: " + key + ": " + obj);
		return obj;
	}

}
